import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from google.colab import drive
from PIL import Image
drive.mount('/content/drive')
Mounted at /content/drive
train_dir = 'drive/My Drive/Colab Notebooks/skin-cancer-isic-data/Train'
test_dir = 'drive/My Drive/Colab Notebooks/skin-cancer-isic-data/Test'
# Get class names (folder names)
class_names = sorted(os.listdir(train_dir))
num_classes = len(class_names)
# Create label mapping
label_map = {i: class_name for i, class_name in enumerate(class_names)}
print("Class mapping:")
for i, class_name in label_map.items():
print(f"{i}: {class_name}")
# Create DataFrames for train and test
def create_dataframe(directory):
data = []
for class_label, class_name in enumerate(class_names):
class_dir = os.path.join(directory, class_name)
for filename in os.listdir(class_dir):
if filename.endswith('.jpg'):
image_path = os.path.join(class_dir, filename)
data.append({'image_path': image_path, 'label': class_label})
return pd.DataFrame(data)
train_df = create_dataframe(train_dir)
test_df = create_dataframe(test_dir)
# Combine for EDA
df = pd.concat([train_df, test_df], ignore_index=True)
print(f"\nTotal images: {len(df)}")
print(f"Number of classes: {num_classes}")
df.head()
Class mapping: 0: actinic keratosis 1: basal cell carcinoma 2: dermatofibroma 3: melanoma 4: nevus 5: pigmented benign keratosis 6: seborrheic keratosis 7: squamous cell carcinoma 8: vascular lesion Total images: 2357 Number of classes: 9
| image_path | label | |
|---|---|---|
| 0 | drive/My Drive/Colab Notebooks/skin-cancer-isi... | 0 |
| 1 | drive/My Drive/Colab Notebooks/skin-cancer-isi... | 0 |
| 2 | drive/My Drive/Colab Notebooks/skin-cancer-isi... | 0 |
| 3 | drive/My Drive/Colab Notebooks/skin-cancer-isi... | 0 |
| 4 | drive/My Drive/Colab Notebooks/skin-cancer-isi... | 0 |
1) RGB → Grayscale Dönüşümü¶
import random
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
plt.figure(figsize=(15, 10))
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
# Formula: 0.2989 * R + 0.5870 * G + 0.1140 * B
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Original RGB image
plt.subplot(3, 6, 2*i + 1)
plt.imshow(rgb_image)
plt.title(f"RGB {i+1}")
plt.axis('off')
# Grayscale image
plt.subplot(3, 6, 2*i + 2)
plt.imshow(grayscale_np, cmap='gray')
plt.title(f"Grayscale {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
2) Pre-Processing¶
2.1 Crop¶
import random
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
plt.figure(figsize=(15, 15))
# Define cropping percentage (e.g., 10% from each side)
cropping_percentage = 0.10 # Crop 10% from each side (top, bottom, left, right)
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
# Formula: 0.2989 * R + 0.5870 * G + 0.1140 * B
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Original Grayscale image
plt.subplot(9, 2, 2*i + 1) # 9 rows, 2 columns = 18 positions
plt.imshow(grayscale_np, cmap='gray')
plt.title(f"Grayscale {i+1}")
plt.axis('off')
# Cropped Grayscale image
plt.subplot(9, 2, 2*i + 2)
plt.imshow(cropped_grayscale_np, cmap='gray')
plt.title(f"Cropped Grayscale {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
import random
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
plt.figure(figsize=(15, 15))
# Define cropping percentage (e.g., 10% from each side)
cropping_percentage = 0.10 # Crop 10% from each side (top, bottom, left, right)
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
# Formula: 0.2989 * R + 0.5870 * G + 0.1140 * B
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Original Grayscale image
plt.subplot(3, 6, 2*i + 1) # Changed from 3,4 back to 3,6 to accommodate 9 pairs of images
plt.imshow(grayscale_np, cmap='gray')
plt.title(f"Grayscale {i+1}")
plt.axis('off')
# Cropped Grayscale image
plt.subplot(3, 6, 2*i + 2)
plt.imshow(cropped_grayscale_np, cmap='gray')
plt.title(f"Cropped Grayscale {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
Kırpma Stratejisi Yorumu¶
Uygulanan sabit yüzde kırpma stratejisi, görüntülerin çevresindeki gereksiz arka plan bilgilerini ortadan kaldırarak odak noktasını artırmayı amaçlar. Bu yöntem, lezyon gibi önemli bölgelerin (Region of Interest - ROI) görüntünün merkezine yakın olma olasılığı yüksek olduğundan, bu bölgeleri kaybetmeden görsel gürültüyü ve ilgisiz kenar ayrıntılarını etkili bir şekilde azaltır. Böylece, modelin yalnızca en alakalı özelliklere odaklanmasına yardımcı olur ve potansiyel olarak daha iyi öğrenme performansı sağlar.
import cv2
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
plt.figure(figsize=(18, 15)) # Adjusted figure size to accommodate 3 images per row for better visibility
# Define cropping percentage (e.g., 10% from each side)
cropping_percentage = 0.10 # Crop 10% from each side (top, bottom, left, right)
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Original Cropped Grayscale image
plt.subplot(3, 3, i + 1)
plt.imshow(equalized_grayscale_np, cmap='gray')
plt.title(f"Equalized Grayscale {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
Output hidden; open in https://colab.research.google.com to view.
import cv2
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
plt.figure(figsize=(18, 15)) # Adjusted figure size to accommodate 9 pairs of images
# Define cropping percentage (e.g., 10% from each side)
cropping_percentage = 0.10 # Crop 10% from each side (top, bottom, left, right)
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Original Cropped Grayscale image
plt.subplot(3, 6, 2*i + 1) # Changed to 3 rows, 6 columns for 9 pairs (18 images total)
plt.imshow(cropped_grayscale_np, cmap='gray')
plt.title(f"Cropped Gray {i+1}")
plt.axis('off')
# Equalized Grayscale image
plt.subplot(3, 6, 2*i + 2)
plt.imshow(equalized_grayscale_np, cmap='gray')
plt.title(f"Equalized Gray {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
Kontrast İyileştirme Yöntemi Yorumu¶
HGörselleştirmelerde kontrast iyileştirme için Histogram Eşitleme yöntemini seçmemizin temel nedeni, görüntünün tüm parlaklık aralığındaki piksel yoğunluğunu daha homojen bir şekilde dağıtarak görüntünün genel kontrastını otomatik olarak artırmasıdır. Özellikle karanlık veya çok aydınlık bölgelerde detayların kaybolduğu durumlarda, bu yöntem pikselleri daha geniş bir dinamik aralığa yayarak bu detayların görünürlüğünü önemli ölçüde iyileştirir.
Görüntü Üzerindeki Etkileri:
- Detayların Ortaya Çıkması: Özellikle düşük kontrastlı görüntülerde, daha önce belirsiz olan lezyon sınırları, doku farklılıkları gibi ince detaylar daha belirgin hale gelir.
- Genel Görünürlük: Görüntüdeki en koyu ve en açık tonlar arasındaki farkı artırarak, insan gözü için daha anlaşılır ve bilgilendirici bir görsel sunar.
Histogram Üzerindeki Etkileri:
- Eşit Piksel Dağılımı: İşlemden önce belirli parlaklık aralıklarında yoğunlaşan pikseller, Histogram Eşitleme sonrası tüm 0-255 (8-bit görüntü için) piksel değer aralığına daha eşit bir şekilde dağılır.
- Daha Düz Bir Histogram: Orijinal görüntü histogramındaki sivri uçlar veya boşluklar azalır ve daha düz, daha geniş bir histogram elde edilir. Bu, görüntünün dinamik aralığının daha etkin kullanıldığını gösterir.
Bu yöntem, özellikle tıbbi görüntülerde lezyonların daha iyi teşhis edilmesi veya diğer ön işleme adımlarına temel oluşturması açısından faydalıdır.
2.3 Gürültü Azaltma / Blurring¶
import cv2
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
plt.figure(figsize=(18, 15)) # Adjusted figure size to accommodate 9 pairs of images
# Define cropping percentage (e.g., 10% from each side)
cropping_percentage = 0.10 # Crop 10% from each side (top, bottom, left, right)
# Define median blur kernel size
median_blur_kernel_size = 5
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Apply Median Blur to the contrast-enhanced image
median_blurred_np = cv2.medianBlur(equalized_grayscale_np, median_blur_kernel_size)
# Plot Contrast Enhanced image
plt.subplot(3, 6, 2*i + 1) # 3 rows, 6 columns for 9 pairs (18 images total)
plt.imshow(equalized_grayscale_np, cmap='gray')
plt.title(f"Contrast Enhanced {i+1}")
plt.axis('off')
# Plot Median Blurred image
plt.subplot(3, 6, 2*i + 2)
plt.imshow(median_blurred_np, cmap='gray')
plt.title(f"Median Blurred {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
Gürültü Azaltma Yöntemi Yorumu¶
Median Blur (Medyan Bulanıklığı) yöntemini gürültü azaltma için seçmemizin temel nedeni, görüntüdeki tuz ve biber gürültüsü (salt-and-pepper noise) gibi ani ve keskin parlaklık değişikliklerini etkili bir şekilde gidermesidir. Bu yöntem, her pikseli komşularının medyan değeriyle değiştirerek çalışır. Bu sayede, kenar bilgilerini diğer bulanıklaştırma yöntemlerine (örneğin Gaussian Blur) göre daha iyi korurken gürültüyü azaltır.
Görüntü Üzerindeki Etkileri:
- Gürültü Azaltma: Özellikle lezyonların dış hatlarını veya iç dokusunu etkileyen rastgele parlak/koyu noktaları ortadan kaldırır.
- Kenar Koruma: Medyan filtresi, görüntüdeki belirgin kenarları bulanıklaştırmadan korur, bu da teşhis için önemli olan yapısal bilgilerin muhafaza edilmesine yardımcı olur.
- Daha Pürüzsüz Görüntü: Genel olarak daha pürüzsüz ve homojen bir görüntü elde edilir, bu da sonraki görüntü işleme adımlarının (segmentasyon gibi) performansını artırabilir.
Seçim Nedenleri:
- Tıbbi görüntülerde sıklıkla görülen rastgele gürültü türlerine karşı dayanıklıdır.
- Daha fazla görüntü bilgisi içeren medyan değeri kullanıldığı için aykırı değerlere (gürültüye) karşı robusttur.
- Özellikle lezyon sınırlarının keskinliğini koruma ihtiyacı duyulan durumlarda tercih edilir.
3) Thresholding ile Segmentasyon¶
3.1 Eşik Değerlerinin Belirlenmesi¶
from skimage.filters import threshold_mean, threshold_otsu, threshold_li
print("Imported thresholding functions from skimage.filters.")
Imported thresholding functions from skimage.filters.
import cv2
import random
from skimage.filters import threshold_mean, threshold_otsu, threshold_li
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
# Define cropping percentage (e.g., 10% from each side)
cropping_percentage = 0.10 # Crop 10% from each side (top, bottom, left, right)
# Define median blur kernel size
median_blur_kernel_size = 5
print("Calculating threshold values for 9 random images:")
for i, image_path in enumerate(random_image_paths):
print(f"\n--- Image {i+1}: {os.path.basename(image_path)} ---")
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Apply Median Blur to the contrast-enhanced image
median_blurred_np = cv2.medianBlur(equalized_grayscale_np, median_blur_kernel_size)
# Calculate Global Threshold (Mean)
global_thresh = threshold_mean(median_blurred_np)
print(f" Global Threshold (Mean): {global_thresh:.2f}")
# Calculate Otsu Threshold
otsu_thresh = threshold_otsu(median_blurred_np)
print(f" Otsu Threshold: {otsu_thresh:.2f}")
# Calculate Li Threshold
li_thresh = threshold_li(median_blurred_np)
print(f" Li Threshold: {li_thresh:.2f}")
Calculating threshold values for 9 random images: --- Image 1: ISIC_0029309.jpg --- Global Threshold (Mean): 129.64 Otsu Threshold: 124.00 Li Threshold: 104.03 --- Image 2: ISIC_0028224.jpg --- Global Threshold (Mean): 130.14 Otsu Threshold: 125.00 Li Threshold: 106.14 --- Image 3: ISIC_0026912.jpg --- Global Threshold (Mean): 130.00 Otsu Threshold: 125.00 Li Threshold: 110.32 --- Image 4: ISIC_0010050.jpg --- Global Threshold (Mean): 129.05 Otsu Threshold: 126.00 Li Threshold: 102.99 --- Image 5: ISIC_0027973.jpg --- Global Threshold (Mean): 130.55 Otsu Threshold: 128.00 Li Threshold: 103.95 --- Image 6: ISIC_0027577.jpg --- Global Threshold (Mean): 129.56 Otsu Threshold: 126.00 Li Threshold: 105.67 --- Image 7: ISIC_0000099.jpg --- Global Threshold (Mean): 128.93 Otsu Threshold: 128.00 Li Threshold: 105.10 --- Image 8: ISIC_0032897.jpg --- Global Threshold (Mean): 130.03 Otsu Threshold: 124.00 Li Threshold: 104.97 --- Image 9: ISIC_0026525.jpg --- Global Threshold (Mean): 130.75 Otsu Threshold: 123.00 Li Threshold: 107.82
3.2 Binary Görüntü Üretimi ve Yöntem Seçimi¶
import cv2
import random
import matplotlib.pyplot as plt
from skimage.filters import threshold_mean, threshold_otsu, threshold_li
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
# Define cropping percentage (e.g., 10% from each side)
cropping_percentage = 0.10 # Crop 10% from each side (top, bottom, left, right)
# Define median blur kernel size
median_blur_kernel_size = 5
plt.figure(figsize=(15, 30)) # Adjust figure size for better visibility of 9x4 grid
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Apply Median Blur to the contrast-enhanced image
median_blurred_np = cv2.medianBlur(equalized_grayscale_np, median_blur_kernel_size)
# Calculate threshold values
global_thresh = threshold_mean(median_blurred_np)
otsu_thresh = threshold_otsu(median_blurred_np)
li_thresh = threshold_li(median_blurred_np)
# Create binary masks
global_mask = median_blurred_np > global_thresh
otsu_mask = median_blurred_np > otsu_thresh
li_mask = median_blurred_np > li_thresh
# Plotting
# Median Blurred Image
plt.subplot(9, 4, i * 4 + 1)
plt.imshow(median_blurred_np, cmap='gray')
plt.title(f"Median Blurred {i+1}")
plt.axis('off')
# Global Threshold Mask
plt.subplot(9, 4, i * 4 + 2)
plt.imshow(global_mask, cmap='gray')
plt.title(f"Global Mask {i+1}")
plt.axis('off')
# Otsu Threshold Mask
plt.subplot(9, 4, i * 4 + 3)
plt.imshow(otsu_mask, cmap='gray')
plt.title(f"Otsu Mask {i+1}")
plt.axis('off')
# Li Threshold Mask
plt.subplot(9, 4, i * 4 + 4)
plt.imshow(li_mask, cmap='gray')
plt.title(f"Li Mask {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
Thresholding Yöntemi Yorumu¶
Farklı eşikleme yöntemlerinin (Global, Otsu, Li) binary çıktılarını karşılaştırdığımızda, her birinin belirli avantajları ve dezavantajları olduğu görülmektedir.
Global Thresholding (Mean): Bu yöntem, görüntünün genel parlaklık dağılımına dayanarak tek bir eşik değeri belirler. Genellikle basit ve hızlıdır, ancak görüntüler arasındaki parlaklık farklarının yüksek olduğu durumlarda veya görüntünün farklı bölgelerinde kontrastın değiştiği durumlarda yetersiz kalabilir. Özellikle ROI'nin farklı parlaklık seviyelerine sahip olduğu veya arka planla benzer tonlara sahip olduğu durumlarda, ya ROI'nin bir kısmını kaybedebilir ya da istenmeyen arka plan detaylarını dahil edebilir.
Otsu Thresholding: Otsu, histogramdaki iki sınıfın (ön plan ve arka plan) varyansını en aza indiren veya sınıflar arası varyansı en üst düzeye çıkaran optimal bir eşik değeri bulmaya çalışır. Genellikle iyi kontrastlı görüntülerde ve histogramı belirgin iki tepe noktasına sahip görüntülerde oldukça başarılıdır. Lezyon sınırlarını genellikle Global yöntemden daha iyi korur ve daha az gürültü içerir. Ancak, ROI'nin küçük olduğu veya görüntünün çok gürültülü olduğu durumlarda performansı düşebilir.
Li Thresholding: Li yöntemi, görüntünün bilgi teorisi ilkelerine dayanarak bir eşik değeri belirler. Görüntüdeki enformasyon kaybını en aza indiren bir eşik arar. Otsu'ya benzer şekilde iyi sonuçlar verebilir, ancak bazen daha hassas veya daha tutarlı eşikler üretebilir, özellikle zorlu veya düşük kontrastlı görüntülerde. Lezyonların ince sınırlarını ve küçük detaylarını korumada başarılı olabilir.
En Uygun Yöntem Seçimi ve Nedenleri:
Genel olarak, Otsu Thresholding yöntemi, lezyon segmentasyonu için en uygun seçeneklerden biri olarak öne çıkmaktadır. Bunun nedenleri şunlardır:
- Sınır Bütünlüğü: Otsu, ROI (lezyon) ile arka plan arasındaki en iyi ayrımı bulmaya çalıştığı için lezyon sınırlarını genellikle diğer yöntemlere göre daha iyi korur. Bu, segmentasyonun doğruluğu açısından kritik öneme sahiptir.
- Gürültüye Karşı Direnç: Ön işleme adımları (medyan bulanıklaştırma) gürültüyü azaltmış olsa da, Otsu yöntemi, gürültüden etkilenme olasılığı daha düşük olan istatistiksel bir yaklaşım kullanır. Bu sayede, binary maskelerdeki "tuz ve biber" gürültüsü daha az olur.
- ROI Bütünlüğü: Otsu, genellikle ROI'nin hem kopmasını hem de taşmasını minimize eder, yani lezyonun tek bir bütün olarak ayrılmasını sağlarken, arka plandan gereksiz bölgelerin dahil edilmesini engeller. Global thresholding'de görülebilecek olan ROI'nin bir kısmının kaybolması veya Li'de bazen oluşabilecek aşırı hassasiyet nedeniyle küçük gürültülerin dahil edilmesi riskini azaltır.
Bu gözlemler ve nedenler doğrultusunda, Otsu Thresholding, cilt lezyonlarının segmentasyonunda sağlam ve güvenilir sonuçlar elde etmek için tercih edilen yöntem olacaktır.
4) Post-Processing¶
4.1 Morfolojik Operatörler¶
import cv2
import random
import matplotlib.pyplot as plt
from skimage.filters import threshold_otsu
import numpy as np # Ensure numpy is imported
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
# Define pre-processing parameters
cropping_percentage = 0.10 # Crop 10% from each side
median_blur_kernel_size = 5
# Define kernel for morphological operations
# A 3x3 rectangular kernel is a common choice for initial morphological operations
morph_kernel_size = 3
morph_kernel = np.ones((morph_kernel_size, morph_kernel_size), np.uint8)
print(f"Using a {morph_kernel_size}x{morph_kernel_size} rectangular kernel for morphological operations.")
plt.figure(figsize=(15, 36)) # Adjusted figure size for better visibility of 9x4 grid
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Apply Median Blur to the contrast-enhanced image
median_blurred_np = cv2.medianBlur(equalized_grayscale_np, median_blur_kernel_size)
# Apply Otsu Thresholding to get the threshold value
otsu_thresh = threshold_otsu(median_blurred_np)
# Create the initial binary mask using Otsu thresholding
otsu_mask = (median_blurred_np > otsu_thresh).astype(np.uint8) * 255 # Convert boolean to 0/255 for cv2
# Apply Opening morphological operation
# Opening removes small objects and smooths boundaries without affecting large objects.
opened_mask = cv2.morphologyEx(otsu_mask, cv2.MORPH_OPEN, morph_kernel)
# Apply Closing morphological operation
# Closing fills small holes and closes small gaps in objects.
closed_mask = cv2.morphologyEx(otsu_mask, cv2.MORPH_CLOSE, morph_kernel)
# Plotting
# Median Blurred Image
plt.subplot(9, 4, i * 4 + 1)
plt.imshow(median_blurred_np, cmap='gray')
plt.title(f"Median Blurred {i+1}")
plt.axis('off')
# Otsu Binary Mask
plt.subplot(9, 4, i * 4 + 2)
plt.imshow(otsu_mask, cmap='gray')
plt.title(f"Otsu Mask {i+1}")
plt.axis('off')
# Opened Mask
plt.subplot(9, 4, i * 4 + 3)
plt.imshow(opened_mask, cmap='gray')
plt.title(f"Opened Mask {i+1}")
plt.axis('off')
# Closed Mask
plt.subplot(9, 4, i * 4 + 4)
plt.imshow(closed_mask, cmap='gray')
plt.title(f"Closed Mask {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
Using a 3x3 rectangular kernel for morphological operations.
Morfolojik Operatör Yorumu¶
Uygulanan morfolojik operatörler olan Açma (Opening) ve Kapama (Closing) işlemleri, önceki eşikleme adımlarından elde edilen binary maskelerin kalitesini artırmak için kullanılır. Bu operatörler, özellikle görüntülerdeki küçük gürültüleri temizlemek ve lezyon bölgelerinin (ROI) şeklini düzeltmek amacıyla seçilmiştir.
Kernel Şekli ve Boyutunun Seçimi (3x3 Dikdörtgen Kernel):
- Şekil (Dikdörtgen): Dikdörtgen (genellikle kare) kernel, görüntü işlemde en yaygın ve genel amaçlı kullanılan yapısal elementtir. Çoğu durumda, nesnelerin genel şekline iyi uyum sağlar ve hem dikey hem de yatay yönlerdeki detayları işlemeye olanak tanır.
- Boyut (3x3): 3x3 gibi küçük bir kernel boyutu, ince detayları korurken küçük gürültüleri gidermek için ideal bir denge sunar. Daha büyük kerneller daha fazla gürültüyü temizleyebilir ancak aynı zamanda ROI'nin ince özelliklerini veya keskin kenarlarını aşındırabilir ya da genişletebilir. 3x3, gürültü azaltma ile önemli yapıların korunması arasında iyi bir uzlaşma sağlar.
Gürültüyü Azaltırken ROI'nin Korunması:
- Açma (Opening): Bu operatör, Erozyon (Erosion) ve ardından Genişleme (Dilation) işlemlerinin birleşimidir. Amacı, küçük izole gürültü piksellerini veya gereksiz bağlantıları (parazitleri) maskeden temizlemektir. Erozyon, nesneleri küçültür ve küçük gürültüleri kaldırır; ardından Genişleme, orijinal nesnelerin boyutunu büyük ölçüde geri kazandırır ancak gürültü olarak kaldırılan küçük bölgeler geri gelmez. Bu sayede, lezyonun ana hatları korunurken, eşikleme sonucu oluşan küçük 'tuz ve biber' gürültüsü temizlenir.
- Kapama (Closing): Açma'nın tersi olan bu operatör, Genişleme ve ardından Erozyon işlemlerinin birleşimidir. Amacı, nesneler içindeki küçük delikleri veya nesne kenarlarındaki küçük kopuklukları doldurmaktır. Genişleme, nesnelerin boyutunu artırır ve delikleri kapatır; ardından Erozyon, orijinal nesnelerin boyutunu geri kazandırır ancak kapanan delikler kapalı kalır. Bu sayede, lezyon maskesindeki olası iç boşluklar doldurulur ve lezyonun daha bütünleşik bir temsilini sağlar.
Bu morfolojik operatörler, binary maskelerdeki gürültüyü (küçük objeler, delikler, kopukluklar) etkili bir şekilde azaltarak segmentasyon kalitesini artırır. Küçük gürültü piksellerinin temizlenmesi, boşlukların doldurulması ve kenarların düzeltilmesi gibi etkileşimler, lezyonun daha doğru ve pürüzsüz bir temsilini sağlar. Bu da, sonraki analiz adımları veya tanısal kararlar için daha güvenilir bir temel oluşturur.
4.2 Connected Component Labeling – CCL¶
import cv2
import random
import matplotlib.pyplot as plt
from skimage.filters import threshold_otsu
import numpy as np # Ensure numpy is imported
# Get 9 random image paths
random_image_paths = random.sample(df['image_path'].tolist(), 9)
# Define pre-processing parameters
cropping_percentage = 0.10 # Crop 10% from each side
median_blur_kernel_size = 5
# Define kernel for morphological operations
morph_kernel_size = 3
morph_kernel = np.ones((morph_kernel_size, morph_kernel_size), np.uint8)
print(f"Using a {morph_kernel_size}x{morph_kernel_size} rectangular kernel for morphological operations.")
plt.figure(figsize=(15, 30)) # Adjusted figure size for better visibility of 9x2 grid (9 rows, 2 columns)
for i, image_path in enumerate(random_image_paths):
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Apply Median Blur to the contrast-enhanced image
median_blurred_np = cv2.medianBlur(equalized_grayscale_np, median_blur_kernel_size)
# Apply Otsu Thresholding to get the threshold value
otsu_thresh = threshold_otsu(median_blurred_np)
# Create the initial binary mask using Otsu thresholding
# Convert boolean to 0/255 for cv2 operations
otsu_mask = (median_blurred_np > otsu_thresh).astype(np.uint8) * 255
# Apply Closing morphological operation (as post-morphology mask)
# Closing fills small holes and closes small gaps in objects.
post_morphology_mask = cv2.morphologyEx(otsu_mask, cv2.MORPH_CLOSE, morph_kernel)
# Apply Connected Component Labeling
num_labels, labels_map = cv2.connectedComponents(post_morphology_mask)
# Print the number of detected components (excluding background)
print(f"Image {i+1}: Detected components (excluding background): {num_labels - 1}")
# Create a colored visualization of the labels_map
# Normalize labels_map to 0-1 range, ensuring background (label 0) is black
labels_map_normalized = labels_map.astype(float) / np.max(labels_map)
colored_labels = plt.cm.nipy_spectral(labels_map_normalized)
colored_labels[labels_map == 0] = 0 # Set background to black
# Plotting
# Post-morphology Mask
plt.subplot(9, 2, 2*i + 1) # 9 rows, 2 columns
plt.imshow(post_morphology_mask, cmap='gray')
plt.title(f"Post-Morphology Mask {i+1}")
plt.axis('off')
# Colored CCL Output
plt.subplot(9, 2, 2*i + 2)
plt.imshow(colored_labels)
plt.title(f"CCL Output {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
Using a 3x3 rectangular kernel for morphological operations. Image 1: Detected components (excluding background): 25 Image 2: Detected components (excluding background): 41 Image 3: Detected components (excluding background): 40 Image 4: Detected components (excluding background): 10 Image 5: Detected components (excluding background): 85 Image 6: Detected components (excluding background): 33 Image 7: Detected components (excluding background): 80 Image 8: Detected components (excluding background): 65 Image 9: Detected components (excluding background): 31
import cv2
import random
import matplotlib.pyplot as plt
from skimage.filters import threshold_otsu
import numpy as np # Ensure numpy is imported
from collections import Counter
# Initialize a list to store component counts
component_counts = []
# Define pre-processing parameters (from previous steps)
cropping_percentage = 0.10 # Crop 10% from each side
median_blur_kernel_size = 5
# Define kernel for morphological operations (from previous steps)
morph_kernel_size = 3
morph_kernel = np.ones((morph_kernel_size, morph_kernel_size), np.uint8)
print("Processing all images in the dataset to calculate connected components...")
# Iterate through each image path in the DataFrame
# For demonstration, let's process a subset if df is very large to avoid long execution times
# If you want to process the full dataset, uncomment the line below and comment the next one
# all_image_paths = df['image_path'].tolist()
all_image_paths = random.sample(df['image_path'].tolist(), min(len(df), 100)) # Process a sample of 100 images for speed
for i, image_path in enumerate(all_image_paths):
if (i + 1) % 100 == 0:
print(f"Processed {i + 1}/{len(all_image_paths)} images...")
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Apply Median Blur to the contrast-enhanced image
median_blurred_np = cv2.medianBlur(equalized_grayscale_np, median_blur_kernel_size)
# Apply Otsu Thresholding
otsu_thresh = threshold_otsu(median_blurred_np)
# Create binary mask (convert boolean to 0/255 for cv2 operations)
otsu_mask = (median_blurred_np > otsu_thresh).astype(np.uint8) * 255
# Apply Morphological Closing
post_morphology_mask = cv2.morphologyEx(otsu_mask, cv2.MORPH_CLOSE, morph_kernel)
# Apply Connected Component Labeling
num_labels, labels_map = cv2.connectedComponents(post_morphology_mask)
# Store the number of components (excluding background)
component_counts.append(num_labels - 1)
print("\n--- CCL Distribution Analysis ---")
# Analyze the distribution of component counts
count_distribution = Counter(component_counts)
print("Component Count Distribution (excluding background):")
for count, freq in sorted(count_distribution.items()):
print(f" {count} component(s): {freq} images")
num_images_one_component = count_distribution.get(1, 0)
num_images_multiple_components = sum(freq for count, freq in count_distribution.items() if count > 1)
print(f"\nSummary:")
print(f" Images with 1 component: {num_images_one_component}")
print(f" Images with more than 1 component: {num_images_multiple_components}")
print(f" Total images processed: {len(all_image_paths)}")
print("\n--- Strategy for Multiple Components ---")
print("Ideally, each image is expected to contain a single Region of Interest (ROI), which corresponds to one lesion. However, the analysis shows that some images contain more than one detected component after segmentation.")
print("When more than one label is detected, the following strategies can be considered:")
print("1. **Identify Noise or Fragmented Lesions:** Multiple components can arise from genuine multiple lesions, noise not fully removed by pre-processing, or a single lesion being fragmented into several parts due to challenging intensity variations or segmentation errors.")
print("2. **Selection of the Largest Component (Most Common Strategy):** If the primary goal is to isolate the main lesion, the most common strategy is to select the largest connected component by area. This approach assumes that the largest component is most likely the actual lesion, and smaller components are either noise or less significant structures. This helps in focusing on the primary ROI and discarding irrelevant small fragments.")
print("3. **Elimination of Small Components:** Components smaller than a certain pixel area threshold can be considered noise or artifacts and can be simply removed. This is often applied in conjunction with selecting the largest component or as an initial filtering step.")
print("4. **Merging of Close Components:** If multiple components are found to be very close to each other, they might represent parts of a single, slightly fragmented lesion. In such cases, further morphological operations (like additional closing with a larger kernel) or distance-based clustering algorithms could be used to merge these components into a single, cohesive ROI.")
print("5. **Multi-label Analysis (if applicable):** In scenarios where multiple distinct lesions per image are expected and relevant for analysis, each component could be treated as a separate ROI for individual analysis or feature extraction. However, for a single primary lesion focus, this is less desirable.")
print("For this task, given the expectation of a single ROI, a robust strategy would involve **selecting the largest connected component and discarding all others**, after a final morphological closing to merge minor fragments. This provides the most unambiguous representation of the primary lesion for subsequent steps like feature extraction.")
Processing all images in the dataset to calculate connected components... Processed 100/100 images... --- CCL Distribution Analysis --- Component Count Distribution (excluding background): 4 component(s): 1 images 6 component(s): 1 images 8 component(s): 1 images 9 component(s): 2 images 12 component(s): 3 images 16 component(s): 1 images 17 component(s): 1 images 18 component(s): 4 images 19 component(s): 1 images 20 component(s): 1 images 21 component(s): 1 images 22 component(s): 2 images 24 component(s): 3 images 25 component(s): 1 images 26 component(s): 1 images 27 component(s): 4 images 28 component(s): 1 images 31 component(s): 3 images 32 component(s): 3 images 33 component(s): 2 images 34 component(s): 1 images 36 component(s): 1 images 37 component(s): 2 images 38 component(s): 3 images 40 component(s): 2 images 43 component(s): 1 images 44 component(s): 1 images 45 component(s): 2 images 47 component(s): 1 images 48 component(s): 1 images 49 component(s): 2 images 51 component(s): 2 images 52 component(s): 1 images 53 component(s): 1 images 56 component(s): 1 images 57 component(s): 1 images 60 component(s): 1 images 61 component(s): 1 images 62 component(s): 3 images 64 component(s): 1 images 65 component(s): 1 images 66 component(s): 3 images 68 component(s): 1 images 69 component(s): 1 images 73 component(s): 3 images 78 component(s): 1 images 80 component(s): 2 images 81 component(s): 1 images 84 component(s): 2 images 85 component(s): 1 images 87 component(s): 1 images 89 component(s): 1 images 90 component(s): 1 images 99 component(s): 1 images 101 component(s): 1 images 105 component(s): 1 images 107 component(s): 1 images 115 component(s): 1 images 122 component(s): 1 images 137 component(s): 1 images 246 component(s): 1 images 277 component(s): 1 images 283 component(s): 1 images 327 component(s): 1 images 396 component(s): 1 images 402 component(s): 1 images 530 component(s): 1 images 2994 component(s): 1 images Summary: Images with 1 component: 0 Images with more than 1 component: 100 Total images processed: 100 --- Strategy for Multiple Components --- Ideally, each image is expected to contain a single Region of Interest (ROI), which corresponds to one lesion. However, the analysis shows that some images contain more than one detected component after segmentation. When more than one label is detected, the following strategies can be considered: 1. **Identify Noise or Fragmented Lesions:** Multiple components can arise from genuine multiple lesions, noise not fully removed by pre-processing, or a single lesion being fragmented into several parts due to challenging intensity variations or segmentation errors. 2. **Selection of the Largest Component (Most Common Strategy):** If the primary goal is to isolate the main lesion, the most common strategy is to select the largest connected component by area. This approach assumes that the largest component is most likely the actual lesion, and smaller components are either noise or less significant structures. This helps in focusing on the primary ROI and discarding irrelevant small fragments. 3. **Elimination of Small Components:** Components smaller than a certain pixel area threshold can be considered noise or artifacts and can be simply removed. This is often applied in conjunction with selecting the largest component or as an initial filtering step. 4. **Merging of Close Components:** If multiple components are found to be very close to each other, they might represent parts of a single, slightly fragmented lesion. In such cases, further morphological operations (like additional closing with a larger kernel) or distance-based clustering algorithms could be used to merge these components into a single, cohesive ROI. 5. **Multi-label Analysis (if applicable):** In scenarios where multiple distinct lesions per image are expected and relevant for analysis, each component could be treated as a separate ROI for individual analysis or feature extraction. However, for a single primary lesion focus, this is less desirable. For this task, given the expectation of a single ROI, a robust strategy would involve **selecting the largest connected component and discarding all others**, after a final morphological closing to merge minor fragments. This provides the most unambiguous representation of the primary lesion for subsequent steps like feature extraction.
CCL Stratejisi Yorumu¶
Ideally, each image is expected to contain a single Region of Interest (ROI), which corresponds to one lesion. However, the analysis shows that some images contain more than one detected component after segmentation. When more than one label is detected, the following strategies can be considered:
- Identify Noise or Fragmented Lesions: Multiple components can arise from genuine multiple lesions, noise not fully removed by pre-processing, or a single lesion being fragmented into several parts due to challenging intensity variations or segmentation errors.
- Selection of the Largest Component (Most Common Strategy): If the primary goal is to isolate the main lesion, the most common strategy is to select the largest connected component by area. This approach assumes that the largest component is most likely the actual lesion, and smaller components are either noise or less significant structures. This helps in focusing on the primary ROI and discarding irrelevant small fragments.
- Elimination of Small Components: Components smaller than a certain pixel area threshold can be considered noise or artifacts and can be simply removed. This is often applied in conjunction with selecting the largest component or as an initial filtering step.
- Merging of Close Components: If multiple components are found to be very close to each other, they might represent parts of a single, slightly fragmented lesion. In such cases, further morphological operations (like additional closing with a larger kernel) or distance-based clustering algorithms could be used to merge these components into a single, cohesive ROI.
- Multi-label Analysis (if applicable): In scenarios where multiple distinct lesions per image are expected and relevant for analysis, each component could be treated as a separate ROI for individual analysis or feature extraction. However, for a single primary lesion focus, this is less desirable. For this task, given the expectation of a single ROI, a robust strategy would involve selecting the largest connected component and discarding all others, after a final morphological closing to merge minor fragments. This provides the most unambiguous representation of the primary lesion for subsequent steps like feature extraction.
5) Öznitelik (Feature) Çıkarımı¶
5.1 First-Order (İstatistiksel) Özellikler¶
import cv2
import random
import matplotlib.pyplot as plt
from skimage.filters import threshold_otsu
from skimage.measure import shannon_entropy
import numpy as np # Ensure numpy is imported
import pandas as pd # Import pandas for DataFrame
from scipy.stats import skew, kurtosis # Import for skewness and kurtosis
# Initialize a list to store the extracted features for each image
features_list = []
# Define pre-processing parameters
cropping_percentage = 0.10 # Crop 10% from each side
median_blur_kernel_size = 5
# Define kernel for morphological operations (3x3 rectangular kernel)
morph_kernel_size = 3
morph_kernel = np.ones((morph_kernel_size, morph_kernel_size), np.uint8)
print("Extracting first-order features from all images in the dataset...")
# Iterate through each image path in the DataFrame
# Use all_image_paths from the previous cell for consistency
# If df is very large, consider processing a subset or displaying progress
all_image_paths = df['image_path'].tolist() # Process the full dataset as requested
# Display progress for long-running operations
progress_interval = max(1, len(all_image_paths) // 10) # Print progress every 10% of images
for idx, image_path in enumerate(all_image_paths):
if (idx + 1) % progress_interval == 0:
print(f"Processed {idx + 1}/{len(all_image_paths)} images...")
# Read RGB image
rgb_image = Image.open(image_path)
# Convert to NumPy array for grayscale conversion
rgb_np = np.array(rgb_image)
# Perform rgb2gray conversion
grayscale_np = np.dot(rgb_np[...,:3], [0.2989, 0.5870, 0.1140]).astype(np.uint8)
# Get image dimensions
height, width = grayscale_np.shape
# Calculate cropping boundaries
crop_top = int(height * cropping_percentage)
crop_bottom = int(height * (1 - cropping_percentage))
crop_left = int(width * cropping_percentage)
crop_right = int(width * (1 - cropping_percentage))
# Apply cropping to the grayscale image
cropped_grayscale_np = grayscale_np[crop_top:crop_bottom, crop_left:crop_right]
# Apply Histogram Equalization to the cropped grayscale image
equalized_grayscale_np = cv2.equalizeHist(cropped_grayscale_np)
# Apply Median Blur to the contrast-enhanced image
median_blurred_np = cv2.medianBlur(equalized_grayscale_np, median_blur_kernel_size)
# Apply Otsu Thresholding
otsu_thresh = threshold_otsu(median_blurred_np)
# Create binary mask (convert boolean to 0/255 for cv2 operations)
otsu_mask = (median_blurred_np > otsu_thresh).astype(np.uint8) * 255
# Apply Morphological Closing
post_morphology_mask = cv2.morphologyEx(otsu_mask, cv2.MORPH_CLOSE, morph_kernel)
# Apply Connected Component Labeling with stats
num_labels, labels_map, stats, centroids = cv2.connectedComponentsWithStats(post_morphology_mask)
# Find the largest component (excluding background)
largest_component_label = 0
max_area = 0
if num_labels > 1: # Ensure there are foreground components
for label in range(1, num_labels): # Iterate through all labels except background (label 0)
area = stats[label, cv2.CC_STAT_AREA]
if area > max_area:
max_area = area
largest_component_label = label
# Create final ROI mask containing only the largest component
final_roi_mask = (labels_map == largest_component_label)
# Extract grayscale pixel values from the median_blurred_np within the ROI
roi_pixels = median_blurred_np[final_roi_mask]
# Initialize feature dictionary
features = {
'image_path': image_path,
'original_label': df.loc[idx, 'label'] # Assuming df is indexed the same as all_image_paths
}
# Calculate first-order statistical features
if len(roi_pixels) > 0: # Ensure ROI is not empty
features['mean'] = np.mean(roi_pixels)
features['std'] = np.std(roi_pixels)
features['variance'] = np.var(roi_pixels)
features['min'] = np.min(roi_pixels)
features['max'] = np.max(roi_pixels)
features['median'] = np.median(roi_pixels)
features['skewness'] = skew(roi_pixels)
features['kurtosis'] = kurtosis(roi_pixels)
features['entropy'] = shannon_entropy(roi_pixels) if len(np.unique(roi_pixels)) > 1 else 0 # Handle uniform images for entropy
features['energy'] = np.sum(roi_pixels.astype(float)**2) # Cast to float to avoid overflow
else:
# Assign NaN or 0 if no ROI is found or it's empty
features['mean'] = np.nan
features['std'] = np.nan
features['variance'] = np.nan
features['min'] = np.nan
features['max'] = np.nan
features['median'] = np.nan
features['skewness'] = np.nan
features['kurtosis'] = np.nan
features['entropy'] = np.nan
features['energy'] = np.nan
features_list.append(features)
# Create a pandas DataFrame from the collected features
df_features = pd.DataFrame(features_list)
print("First-order feature extraction complete.")
print("Displaying the first 5 rows of the features DataFrame:")
print(df_features.head())
Extracting first-order features from all images in the dataset...
Processed 235/2357 images...
Processed 470/2357 images...
Processed 705/2357 images...
Processed 940/2357 images...
Processed 1175/2357 images...
Processed 1410/2357 images...
Processed 1645/2357 images...
Processed 1880/2357 images...
Processed 2115/2357 images...
Processed 2350/2357 images...
First-order feature extraction complete.
Displaying the first 5 rows of the features DataFrame:
image_path original_label \
0 drive/My Drive/Colab Notebooks/skin-cancer-isi... 0
1 drive/My Drive/Colab Notebooks/skin-cancer-isi... 0
2 drive/My Drive/Colab Notebooks/skin-cancer-isi... 0
3 drive/My Drive/Colab Notebooks/skin-cancer-isi... 0
4 drive/My Drive/Colab Notebooks/skin-cancer-isi... 0
mean std variance min max median skewness kurtosis \
0 191.384284 37.020147 1370.491255 106 255 189.0 -0.019354 -1.180212
1 197.906433 36.605681 1339.975848 42 255 203.0 -0.235537 -1.133394
2 200.059758 33.938965 1151.853315 101 255 202.0 -0.226483 -0.969128
3 191.163241 37.099975 1376.408132 71 255 192.0 -0.044596 -1.166718
4 195.787221 37.052012 1372.851616 35 255 193.0 -0.050941 -1.149394
entropy energy
0 4.846855 3.395084e+09
1 4.759624 2.707038e+09
2 5.341759 3.044865e+09
3 5.544938 3.294851e+09
4 5.186463 1.597709e+09
5.1 First-Order (İstatistiksel) Özellikler Yorumu¶
Aşağıda, lezyon bölgelerinden çıkarılan birinci dereceden istatistiksel özniteliklerin ilk 5 örneği gösterilmiştir. Bu öznitelikler, lezyonun parlaklık dağılımı hakkında önemli bilgiler sağlayarak, renk ve dokusal homojenlik gibi özelliklerin sayısal olarak ifade edilmesine olanak tanır.
{{df_features.head().to_markdown(index=False)}}
Özniteliklerin Lezyon Bölgeleri Hakkında Sağladığı Bilgiler:
Mean (Ortalama): Lezyon bölgesindeki piksellerin ortalama parlaklık değerini temsil eder. Yüksek bir ortalama değer, lezyonun daha aydınlık olduğunu, düşük bir değer ise daha koyu olduğunu gösterir. Bu, lezyonun genel tonunu ve çevresindeki deriye göre kontrastını anlamak için temel bir göstergedir.
Standard Deviation (Standart Sapma): Lezyon içindeki piksel parlaklıklarının ortalamadan ne kadar saptığını ölçer. Düşük standart sapma, lezyonun renk/parlaklık açısından homojen olduğunu (örneğin tek tip renkte), yüksek standart sapma ise daha çeşitli tonlara sahip olduğunu (örneğin heterojen bir görünümde) gösterir. Bu, dokusal homojenlik veya heterojenlik için önemli bir göstergedir.
Variance (Varyans): Standart sapmanın karesi olup, piksel değerlerinin ortalamadan ne kadar yayıldığının bir başka ölçüsüdür. Standart sapma ile benzer bilgiler sunar, ancak genlikteki değişikliklere daha duyarlıdır. Lezyonun iç yapısının ne kadar değişken olduğunu gösterir.
Min (Minimum): Lezyon bölgesindeki en koyu pikselin parlaklık değeridir. Lezyonun içindeki en karanlık noktanın şiddetini belirtir ve genellikle gölgelenmeler veya çok koyu pigmentasyon alanları hakkında bilgi verebilir.
Max (Maksimum): Lezyon bölgesindeki en aydınlık pikselin parlaklık değeridir. Lezyonun içindeki en parlak noktanın şiddetini belirtir ve aydınlık bölgelerin veya yansımaların varlığını gösterebilir.
Median (Medyan): Lezyon bölgesindeki piksellerin parlaklık değerlerinin ortanca değeridir. Aykırı değerlerden (gürültü, çok parlak veya çok koyu noktalar) ortalamaya göre daha az etkilenir, bu nedenle lezyonun 'tipik' parlaklığını daha sağlam bir şekilde temsil edebilir.
Skewness (Çarpıklık): Piksel parlaklık değerlerinin dağılımının simetrisini ölçer. Pozitif çarpıklık, dağılımın sağa doğru kuyruklu olduğunu (yani daha fazla koyu piksel olduğunu), negatif çarpıklık ise sola doğru kuyruklu olduğunu (yani daha fazla aydınlık piksel olduğunu) gösterir. Lezyonun baskın parlaklık tonunun hangi yöne kaydığını anlamaya yardımcı olur.
Kurtosis (Basıklık): Piksel parlaklık değerlerinin dağılımının sivrilik derecesini ölçer. Yüksek basıklık (leptokurtik), dağılımın ortalamaya yakın çok sayıda pikselle 'sivri' olduğunu; düşük basıklık (platikurtik) ise dağılımın daha 'düz' ve piksellerin daha geniş bir aralığa yayıldığını gösterir. Bu, lezyonun parlaklıklarının belirli bir aralıkta yoğunlaşıp yoğunlaşmadığı hakkında bilgi verir.
Entropy (Entropi): Lezyon bölgesindeki piksel parlaklık dağılımının rastgeleliğini veya düzensizliğini ölçer. Yüksek entropi, piksellerin daha çeşitli ve rastgele dağıldığını (yani karmaşık bir dokuya sahip olduğunu), düşük entropi ise daha düzenli veya tekdüze bir dağılım olduğunu gösterir. Bu, lezyonun dokusal karmaşıklığı için değerli bir ölçümdür.
Energy (Enerji): Görüntünün dokusal homojenliğini veya tekdüzeliğini ölçer. Yüksek enerji, görüntünün daha düzenli veya tekdüze olduğunu (belirli piksel değerlerinin tekrar ettiğini), düşük enerji ise daha heterojen olduğunu gösterir. Entropiye benzer şekilde dokusal özellikler hakkında bilgi verir.
Bu öznitelikler bir araya gelerek, lezyonların görsel özelliklerini nicel olarak ifade etmeyi sağlar ve bu da model tabanlı sınıflandırma veya tanı süreçleri için temel bir girdi teşkil eder.